#include "gamesound.h"

const int   INTERFACE_UPDATETIME = 50;      // 50ms update for interface
const float DISTANCEFACTOR = 1.0f;          // Units per meter.  I.e feet would = 3.28.  centimeters would = 100.


gamesound::gamesound()
{		
	listenerflag = true;
	listenerpos.x = 0.0f;
	listenerpos.y = 0.0f;
	listenerpos.z = -1.0f * DISTANCEFACTOR;
	channel = new vector<FMOD::Channel*>();
	sound = new vector<FMOD::Sound*>();
	dsp = new vector<FMOD::DSP*>();
	speakermode = FMOD_SPEAKERMODE_STEREO;
	numberOfSounds = 0;
	numberOfChannels = 0;
	numberOfDsps = 0;
}

void gamesound::ERRCHECK(FMOD_RESULT result)
{
    if (result != FMOD_OK)
	{
		OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "FMOD error! (" + StringConverter::toString(result) + "): " + FMOD_ErrorString(result), "SoundManager::Initialize");
  
    }
}

//Create a System object and initialize.
void gamesound::start(int maxNumberOfChannels)
{
    /*
        Create a System object and initialize.
    */
    result = FMOD::System_Create(&system);
    ERRCHECK(result);
    
    result = system->getVersion(&version);
    ERRCHECK(result);

    if (version < FMOD_VERSION)
    {
        printf("Error!  You are using an old version of FMOD %08x.  This program requires %08x\n", version, FMOD_VERSION);

    }
    
    result = system->getDriverCaps(0, &caps, 0, 0, &speakermode);
    ERRCHECK(result);

    result = system->setSpeakerMode(speakermode);       /* Set the user selected speaker mode. */
    ERRCHECK(result);

    if (caps & FMOD_CAPS_HARDWARE_EMULATED)             /* The user has the 'Acceleration' slider set to off!  This is really bad for latency!. */
    {                                                   /* You might want to warn the user about this. */
        result = system->setDSPBufferSize(1024, 10);
        ERRCHECK(result);
    }

    result = system->getDriverInfo(0, name, 256, 0);
    ERRCHECK(result);

    if (strstr(name, "SigmaTel"))   /* Sigmatel sound devices crackle for some reason if the format is PCM 16bit.  PCM floating point output seems to solve it. */
    {
        result = system->setSoftwareFormat(48000, FMOD_SOUND_FORMAT_PCMFLOAT, 0,0, FMOD_DSP_RESAMPLER_LINEAR);
        ERRCHECK(result);
    }

    result = system->init(maxNumberOfChannels, FMOD_INIT_NORMAL, 0);
	ERRCHECK(result);
    if (result == FMOD_ERR_OUTPUT_CREATEBUFFER)         /* Ok, the speaker mode selected isn't supported by this soundcard.  Switch it back to stereo... */
    {
        result = system->setSpeakerMode(FMOD_SPEAKERMODE_STEREO);
        ERRCHECK(result);
                
        result = system->init(maxNumberOfChannels, FMOD_INIT_NORMAL, 0);/* ... and re-init. */
        ERRCHECK(result);
    }

    
    /*
        Set the distance units. (meters/feet etc).
    */
    result = system->set3DSettings(1.0, DISTANCEFACTOR, 1.0f);
    ERRCHECK(result);

	result = system->setStreamBufferSize(64*1024, FMOD_TIMEUNIT_RAWBYTES);
    ERRCHECK(result);
}

int gamesound::addSound(char *path)
{
	FMOD::Sound *asound;
	sound->push_back(asound);
	int thisSoundsNumber = numberOfSounds;
	//Load some sounds
	result = system->createStream(path, FMOD_SOFTWARE | FMOD_2D, 0, &(*sound)[thisSoundsNumber]);
    ERRCHECK(result);
	result = (*sound)[thisSoundsNumber]->setMode(FMOD_LOOP_OFF);
	ERRCHECK(result);
	++numberOfSounds;
	return thisSoundsNumber;
}

int gamesound::addChannel()
{
	FMOD::Channel* achannel;
	achannel = 0;
	int thisChannelsNumber = numberOfChannels;
	channel->push_back(achannel);
	++numberOfChannels;
	return thisChannelsNumber;
}

void gamesound::play(int soundNumber, int channelNumber)
{
	result = system->playSound(FMOD_CHANNEL_FREE, (*sound)[soundNumber], false, &(*channel)[channelNumber]);
	ERRCHECK(result);
}

void gamesound::updatesound(float x, float y, float z)
{
		static float t = 0;
        static FMOD_VECTOR lastpos = { 0.0f, 0.0f, 0.0f };
        FMOD_VECTOR forward        = { 0.0f, 0.0f, 1.0f };
        FMOD_VECTOR up             = { 0.0f, 1.0f, 0.0f };
        FMOD_VECTOR vel;

		listenerpos.x = - x / 10;
		listenerpos.y = - y / 10;
		listenerpos.z = - z / 10;
       

        // ********* NOTE ******* READ NEXT COMMENT!!!!!
        // vel = how far we moved last FRAME (m/f), then time compensate it to SECONDS (m/s).
        vel.x = (listenerpos.x - lastpos.x) * (1000 / INTERFACE_UPDATETIME);
        vel.y = (listenerpos.y - lastpos.y) * (1000 / INTERFACE_UPDATETIME);
        vel.z = (listenerpos.z - lastpos.z) * (1000 / INTERFACE_UPDATETIME);

        // store pos for next time
        lastpos = listenerpos;

        result = system->set3DListenerAttributes(0, &listenerpos, &vel, &forward, &up);
        ERRCHECK(result);

        t += (30 * (1.0f / (float)INTERFACE_UPDATETIME));    // t is just a time value .. it increments in 30m/s steps in this example
        system->update();

}


void gamesound::shutdown()
{
    /*
        Shut down
    */
	for (int i = 0; i < numberOfSounds; ++i)
	{
		result = (*sound)[i]->release();
		
		ERRCHECK(result);
	}

    result = system->close();
    ERRCHECK(result);
    result = system->release();
    ERRCHECK(result);

}



unsigned int gamesound::timeSinceStart(int channelNumber)
{
	unsigned int ms = 0;
    (*channel)[channelNumber]->getPosition(&ms, FMOD_TIMEUNIT_MS);
	return ms;
}



unsigned int gamesound::length(int channelNumber)
{
	FMOD::Sound *currentsound = 0;
	unsigned int lenms = 0;
	(*channel)[channelNumber]->getCurrentSound(&currentsound);
    if (currentsound)
    {
        result = currentsound->getLength(&lenms, FMOD_TIMEUNIT_MS);
        if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
        {
            ERRCHECK(result);
        }
    }

	return lenms;
}

int gamesound::totalChannels(void)
{
	return numberOfChannels;
}

int gamesound::totalSounds(void)
{
	return numberOfSounds;
}

bool gamesound::getPausedStat(int channelNumber)
{
	bool paused = 0;
	result = (*channel)[channelNumber]->getPaused(&paused);
	if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
	{
		ERRCHECK(result);
	}

	return paused;
}


void gamesound::setPausedStat(bool pausedSat, int channelNumber)
{
	 (*channel)[channelNumber]->setPaused(pausedSat);
}

void gamesound::jumpToPosition(unsigned int timeStampInMicroSec, int channelNumber)
{
	(*channel)[channelNumber]->setPosition(timeStampInMicroSec, FMOD_TIMEUNIT_MS);
}

float gamesound::changeVolume(float delta, int channelNumber)
{
	float volume = 0;
	(*channel)[channelNumber]->getVolume(&volume);
    volume += delta;
    (*channel)[channelNumber]->setVolume(volume);
	return volume;
}

int gamesound::addNoise(int noiseType, int channelNumber,float frequency)
{
	FMOD::DSP *adsp = 0;
	dsp->push_back(adsp);
	int thisDspsNumber = numberOfDsps;
	++numberOfDsps;
	result = system->createDSPByType(FMOD_DSP_TYPE_OSCILLATOR, &(*dsp)[thisDspsNumber]);
    ERRCHECK(result);
	result = (*dsp)[thisDspsNumber]->setParameter(FMOD_DSP_OSCILLATOR_RATE, frequency);       /* musical note 'A' */
    ERRCHECK(result);
	result = system->playDSP(FMOD_CHANNEL_REUSE, (*dsp)[thisDspsNumber], true, &(*channel)[channelNumber]);
	(*channel)[channelNumber]->setVolume(0.25f);
    result = (*dsp)[thisDspsNumber]->setParameter(FMOD_DSP_OSCILLATOR_TYPE, noiseType);
    ERRCHECK(result);	
	(*channel)[channelNumber]->setPaused(false);
	return thisDspsNumber;
}

int gamesound::addPitchShift(int channelNumber)
{
	FMOD::DSP *adsp = 0;
	dsp->push_back(adsp);
	int thisDspsNumber = numberOfDsps;
	++numberOfDsps;
	result = system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &(*dsp)[thisDspsNumber]);
	ERRCHECK(result); 
	// Then apply the DSP effect to the channel
	result = (*channel)[channelNumber]->addDSP((*dsp)[thisDspsNumber],0);
	ERRCHECK(result);

	// Then you need to set the pitch
	result = (*dsp)[thisDspsNumber]->setParameter(0,1); //1 does not make any change
	ERRCHECK(result); 
	return thisDspsNumber;

	
}

void gamesound::setPitch(float value, int dspNumber)
{
	FMOD_DSP_TYPE type;
	(*dsp)[dspNumber]->getType(&type);
	if(type == 11)
	{
	result = (*dsp)[dspNumber]->setParameter(0,value);
	ERRCHECK(result); 
	}
}

void gamesound::setChannelFrequency(float value, int channelNumber)
{
	result= (*channel)[channelNumber]->setFrequency(value);
	ERRCHECK(result); 
}

float gamesound::getChannelFrequency(int channelNumber)
{
	float value = 0;
	result= (*channel)[channelNumber]->getFrequency(&value);
	ERRCHECK(result); 
	return value;
}